package org.oep.jmingle;

import java.io.IOException;
import java.util.Calendar;
import java.util.Enumeration;
import java.util.Hashtable;
import java.util.Vector;

import javax.microedition.lcdui.Image;
import javax.microedition.rms.RecordStoreException;

import org.oep.game.ImageStore;

/**
 * This is a doozy of a class. It handles basically everything associated with a player's
 * progress. 
 * @author pmkilgo
 *
 */
public class MingleTracker {
	private final String defaultName = "???";
	private final String recordName = "MingleDB";
	private final String scoreRecord = "MingleScore";
	
	private final Hashtable mingledDevices;
	private Vector lastScan;
	private Vector thisScan;
	private Vector things = new Vector();
	
	private long score = 0;
	
	public MingleTracker(JMingle parent) {
		thisScan = new Vector();
		lastScan = new Vector();
		mingledDevices = new Hashtable();
	}
	
	
	/*
	 * For external use
	 */
	//--------------------------------------------------------------//
	
	/**
	 * This method returns the number of mingles we have in memory.
	 */
	public int getCacheSize() {
		if(mingledDevices != null)
			return mingledDevices.size();
		else
			return -1;
	}
	
	/**
	 * Iterates through the Hashtable in memory and returns the ath element
	 * in the iteration.
	 * @param a, the index of the element you wish for
	 * @return a MingleNode representing that device
	 */
	public MingleNode deviceAt(int a) {
		//TODO: This is slow! Find a better way to do it
		if(a < 0 || a >= mingledDevices.size())
			throw new IllegalArgumentException("Index out of range");
		
		Enumeration e = mingledDevices.elements();
		
		int i = 0;
		while(e.hasMoreElements()) {
			if(a == i) return (MingleNode) e.nextElement();
			else e.nextElement();
			i++;
		}
		
		throw new IllegalArgumentException("Index out of range");
	}
	
	/**
	 * Returns the number of mingles in storage
	 * @return mingles in storage
	 */
	public int getRecordSize() {
		try {
			return RecordStoreTools.countRecords(recordName);
		}
		catch(RecordStoreException e) {
			return -1;
		}
	}
	
	/**
	 * Synchronizes the mingles in memory to the file system on the device.
	 */
	public void exportToRecordStore() {
		try {
			RecordStoreTools.export(mingledDevices, recordName);
		}
		catch(Exception e) {
			
		}
	}
	
	/**
	 * We can manually invoke the load process as it sometimes can take a long time.
	 */
	public void doLoad() {
		if(mingledDevices == null) return;
		
		try {
			RecordStoreTools.load(recordName, mingledDevices);
		} catch (RecordStoreException e) {
			mingledDevices.clear();
		}
		loadScore();
		loadThings();
	}
	
	/**
	 * Loads all of the "Favorite things" into memory.
	 */
	private void loadThings() {
		things.removeAllElements();
		things.addElement("cake");
		things.addElement("anchor");
		things.addElement("bug");
		things.addElement("sun");
	}

	
	/**
	 * Alias to perform a mingle with an object of type Minglet
	 * @param a, the minglet
	 */
	public void doMingle(Minglet a) {
		doMingle(a.getAddress(), a.getOwner());
	}
	
	
	/**
	 * Call doMingle() with "now" as the timestamp.
	 * @param address
	 * @param name
	 */
	public void doMingle(String address, String name) {
		doMingle(address, name, System.currentTimeMillis());
	}
	
	/**
	 * Performs all tasks associated with mingling.
	 * @param address
	 * @param name
	 */
	public void doMingle(String address, String name, long timestamp) {
		// Fix the default name
		if(name == null)
			name = defaultName;
		if(name.length() == 0)
			name = defaultName;
		
		MingleNode node = null;
		
		// Part 1: Handle the case that the mingle we are performing is with
		// someone we have seen before.
		if(mingledDevices.containsKey(address)){
			boolean updated = false;
			node = (MingleNode) mingledDevices.get(address);
			node.setLastSeen(timestamp);
			
			// In this case, we've seen it before, but it's name has changed
			if( shouldDoUpdate(node.getName(), name) ) {
				node.setName(name);
				try {
					if(RecordStoreTools.update(node, recordName)) {
						updated = true;
					}
				}
				catch (RecordStoreException e) { }
			}
			
			node.bump();
			
			// This is to update the time if it hasn't been already
			try {
				if(!updated)
					RecordStoreTools.update(node, recordName);
			}
			catch(RecordStoreException e) { }
				
			
		}
		
		// It's brand-spanking new
		else {
			node = new MingleNode(name, address);
			node.setLastSeen(timestamp);
			node.bump();
			
			mingledDevices.put(address, node);
			
			// Add it to the Record Store
			try {
				RecordStoreTools.add(node, recordName);
			}
			catch(RecordStoreException e) {
			}
		}
		
		
		System.out.println("Finished mingle");
	}
	
	/**
	 * This erases /everything/. Be careful how you call it.
	 */
	public void eraseAll() {
		mingledDevices.clear();
		thisScan.removeAllElements();
		lastScan.removeAllElements();
		try {
			RecordStoreTools.delete(recordName);
		}
		catch(RecordStoreException e) {
			System.err.println(e);
		}
	}
	
	/**
	 * Do we have this Minglet in our memory bank?
	 * @param a, the minglet
	 * @return true if we do, false if we don't
	 */
	public boolean know(Minglet a) {
		return know(a.getAddress());
	}
	
	/**
	 * Do we have this address in our memory bank?
	 * @param address
	 * @return true if we do, false if we don't
	 */
	public boolean know(String address) {
		return mingledDevices.containsKey( address );
	}
	
	/**
	 * Alias for getLastSeenTime(String);
	 * @param a, a minglet
	 * @return human-friendly last seen time
	 * @throws Exception
	 */
	public String getLastSeenTime(Minglet a) throws Exception {
		return getLastSeenTime(a.getAddress());
	}
	
	/**
	 * Fetches a human-friendly last seen time.
	 * @param address
	 * @return human-friendly last seen time
	 * @throws Exception
	 */
	public String getLastSeenTime(String address) throws Exception {
		MingleNode n = (MingleNode) mingledDevices.get(address);
		
		if(n == null) throw new Exception("No such key");
		
		return Utils.friendlyTimeDiffNow( n.getLastSeen() );
	}
	
	/**
	 * Returns a (human-unfriendly) timestamp
	 * @param address
	 * @return timestamp
	 * @throws Exception
	 */
	public long getRawLastSeenTime(String address) throws Exception {
		MingleNode n = (MingleNode) mingledDevices.get(address);
		
		if(n == null) throw new Exception("No such key");
		System.out.println("Raw time of: " + n.getLastSeen());
		return n.getLastSeen();
	}
	
	/**
	 * Returns a useful node of data for a given device address
	 * @param hash
	 * @return data with fields for the node
	 */
	public MingleNode getNode(String hash) {
		return (MingleNode) mingledDevices.get(hash);
	}


	/**
	 * Decides whether or not we should update a name from 'o' to 'n'
	 * @param o, the old name
	 * @param n, the new name
	 * @return true if we should update, false if otherwise
	 */
	private boolean shouldDoUpdate(String o, String n) {
		// Never update to the default name
		if(n.compareTo(defaultName) == 0) return false;
		
		// Never update if names are the same
		if(o.compareTo(n) == 0) return false;
		
		return true;
	}
	
	/**
	 * An algorithm for randomly assigning a favorite thing
	 * @param id, the id of the bluetooth device
	 * @return an image object for the favorite thing
	 */
	public Image getFavoriteThing(String id) {
		int r = getFavoriteThingID(id);
		String s = (String) things.elementAt(r);
		try {
			return ImageStore.getInstance().get("/res/things/" + s + ".png");
		} catch (IOException e) {
			e.printStackTrace();
			return null;
		}
	}
	
	/**
	 * Determined the ID of the random thing
	 * @param id, the device address to be determined
	 * @return an index in vector 'things' where the filename of the favorite thing is
	 */
	public int getFavoriteThingID(String id) {
		// This modifier causes the ID to vary according to day
		String modifier = String.valueOf( Calendar.getInstance().get(Calendar.YEAR) )
			+ String.valueOf( Calendar.getInstance().get(Calendar.MONTH) )
			+ String.valueOf(Calendar.getInstance().get(Calendar.DAY_OF_MONTH));
		
		return Utils.randWithSeed(modifier, id, things.size());
	}
	
	/**
	 * Load the score to memory from record store
	 */
	public void loadScore() {
		try {
			score = RecordStoreTools.fetchLong(scoreRecord, 1);
		} catch (RecordStoreException e) {
			// The score probably doesn't exist, but print the stack
			// trace just for shits and giggles
			e.printStackTrace();
		}
	}
	
	/**
	 * Write the score in memory to the record store
	 */
	public void writeScore() {
		try {
			if(RecordStoreTools.countRecords(scoreRecord) > 0) {
				RecordStoreTools.storeLong(scoreRecord, 1, score);
			}
			else {
				RecordStoreTools.addLong(scoreRecord, score);
			}
		} catch (RecordStoreException e) {
			e.printStackTrace();
		}
	}
	
	/**
	 * Increment the score and update it to the record store
	 * @param a, the value to increment by
	 */
	public void addScore(long a) {
		score += a;
		writeScore();
	}
	
	/**
	 * Retrieve the score from memory
	 * @return the current score
	 */
	public long getScore() {
		return score;
	}
	
	/**
	 * Retrieve the bonus for matching the same conversation subjects
	 * @return bonus in percent
	 */
	public int getBonus() {
		return (things.size() - 1) * 100 / things.size();
	}
}
